package com.zhss.demo.register.client;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * 服务注册中心客户端缓存服务注册表
 */
public class CachedServiceRegistry {

    /**
     * 服务注册表拉取间隔时间
     */
    private static final Long SERVICE_REGISTRY_FETCH_INTERVAL = 30 * 1000L;
    /**
     * 客户端缓存的所有的服务实例的信息
     * 使用AtomicReference原因：客户端再拉取全量注册表和增量注册表时都会对注册表进行赋值，以后可能还会有其他的情况会对注册表进行赋值
     */
    //private AtomicReference<Applications> applications = new AtomicReference<Applications>(new Applications());
    private AtomicStampedReference<Applications> applications;
    /**
     * 负责定时拉取注册表到客户端进行缓存的后台线程
     */
    private FetchDeltaRegistryWorker fetchDeltaRegistryWorker;
    /**
     * RegisterClient
     */
    private RegisterClient registerClient;
    /**
     * http通信组件
     */
    private HttpSender httpSender;
    /**
     * 代表了当前的本地缓存的服务注册表的一个版本号
     */
    private AtomicLong applicationsVersion = new AtomicLong(0L);

    public CachedServiceRegistry(
            RegisterClient registerClient,
            HttpSender httpSender) {
        this.fetchDeltaRegistryWorker = new FetchDeltaRegistryWorker();
        this.registerClient = registerClient;
        this.httpSender = httpSender;
        //this.applications = new AtomicReference<Applications>(new Applications(), System.currentTimeMillis());
        // 0 相当于一个版本号
        this.applications = new AtomicStampedReference<Applications>(new Applications(), 0);
    }

    /**
     * 初始化
     */
    public void initialize() {
        // 启动全量拉取注册表的线程
        FetchFullRegistryWorker fetchFullRegistryWorker = new FetchFullRegistryWorker();
        fetchFullRegistryWorker.start();
        // 启动增量拉取注册表的线程
        this.fetchDeltaRegistryWorker.start();
    }

    /**
     * 销毁这个组件
     */
    public void destroy() {
        this.fetchDeltaRegistryWorker.interrupt();
    }

    /**
     * 全量拉取注册表的后台线程
     * @author zhonghuashishan
     */
    private class FetchFullRegistryWorker extends Thread {

        @Override
        public void run() {
            // 拉取全量注册表
            // 这个操作要走网络，但是不知道为什么抽风了，此时就是一直卡住，数据没有返回回来
            // 卡在这儿了，卡了几分钟
            // 此时的这个数据是一个旧的版本，里面仅仅包含了30个服务实例
            // 全量拉注册表的线程突然苏醒过来了，此时将
            // 30个服务实例的旧版本的数据赋值给了本地缓存注册表
            fetchFullRegistry();
        }
    }

    private void fetchFullRegistry() {
        // 这里全量拉取注册表的时候，会首先获取版本号，当版本号没有更新得情况下才会取获取全量注册表
        while (true) {
            // 一定要在发起网络请求之前，先拿到一个当时的版本号
            // version = 0
            Long expectedVersion = applicationsVersion.get();
            // 拉取全量注册表
            // 接着在这里发起网络请求，此时可能会有别的线程来修改这个注册表，更新版本，在这个期间
            Applications fetchedApplications = httpSender.fetchFullRegistry();
            // 必须是发起网络请求之后，这个注册表的版本没有被人修改过，此时他才能去修改
            if (applicationsVersion.compareAndSet(expectedVersion, expectedVersion + 1)) {

                // 期望的注册表
                //Applications expectedApplications = applications.get();
                // 获取对象引用
                Applications expectedApplications = applications.getReference();
                // 获取类似邮戳 :为了解决ABA问题 自旋可以使用LongAdd解决
                int expectedStamp = applications.getStamp();
                // 经过CAS操作之后  拉取的全量注册表额引用是否是我希望的注册表的引用（比较的是对象的引用而不是仅仅比较对象的值）
         /*   if(applications.compareAndSet(expectedApplications, fetchedApplications)) {
                break;
            }*/
                if (applications.compareAndSet(expectedApplications, fetchedApplications,
                        expectedStamp, expectedStamp + 1)) {
                    return;
                }
            }
        }
        // 如果在这个期间，有人修改过注册表，版本不一样了，此时就直接if不成立，不要把你拉取到的
        // 旧版本的注册表给设置进去
    }

    /**
     * 增量拉取注册表的后台线程
     * @author zhonghuashishan
     */
    private class FetchDeltaRegistryWorker extends Thread {

        @Override
        public void run() {
            while (registerClient.isRunning()) {
                try {
                    Thread.sleep(SERVICE_REGISTRY_FETCH_INTERVAL);
                    // 先拉了一个增量注册表，发现跟本地合并之后，条数不对
                    Long expectedVersion = applicationsVersion.get();
                    // 拉取回来的是最近3分钟变化的服务实例
                    DeltaRegistry deltaRegistry = httpSender.fetchDeltaRegistry();
                    if (applicationsVersion.compareAndSet(expectedVersion, expectedVersion + 1)) {
                        // 我们这里其实是要大量的修改本地缓存的注册表，所以此处需要加锁
                        mergeDeltaRegistry(deltaRegistry);

                        // 再检查一下，跟服务端的注册表的服务实例的数量相比，是否是一致的
                        // 封装一下增量注册表的对象，也就是拉取增量注册表的时候，一方面是返回那个数据
                        // 另外一方面，是要那个对应的register-server端的服务实例的数量
                        reconcileRegistry(deltaRegistry);
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        /**
         * 合并增量注册表到本地缓存注册表里去
         * @param deltaRegistry
         */
        private void mergeDeltaRegistry(DeltaRegistry deltaRegistry) {
            synchronized (applications) {
                // 注册表从applications中去拿
                // Map<String, Map<String, ServiceInstance>> registry = applications.get().getRegistry();
                Map<String, Map<String, ServiceInstance>> registry = applications.getReference().getRegistry();

                Queue<RecentlyChangedServiceInstance> recentlyChangedQueue = deltaRegistry.getRecentlyChangedQueue();

                for (RecentlyChangedServiceInstance recentlyChangedItem : recentlyChangedQueue) {
                    String serviceName = recentlyChangedItem.serviceInstance.getServiceName();
                    String serviceInstanceId = recentlyChangedItem.serviceInstance.getServiceInstanceId();

                    // 如果是注册操作的话
                    if (ServiceInstanceOperation.REGISTER.equals(recentlyChangedItem.serviceInstanceOperation)) {
                        /**
                         * 			Map<String, ServiceInstance> serviceInstanceMap = registry.get(serviceName);
                         * 						if(serviceInstanceMap == null) {
                         * 							serviceInstanceMap = new HashMap<String, ServiceInstance>();
                         * 							registry.put(serviceName, serviceInstanceMap);
                         *                                                }
                         */
                        Map<String, ServiceInstance> serviceInstanceMap = registry.computeIfAbsent(serviceName,
                                k -> new HashMap<String, ServiceInstance>());

                        /**
                         * 				ServiceInstance serviceInstance = serviceInstanceMap.get(serviceInstanceId);
                         * 						if(serviceInstance == null) {
                         * 							serviceInstanceMap.put(serviceInstanceId,
                         * 									recentlyChangedItem.serviceInstance);
                         *                                                }
                         */
                        serviceInstanceMap.computeIfAbsent(serviceInstanceId, k -> recentlyChangedItem.serviceInstance);
                    }
                    // 如果是删除操作的话
                    else if (ServiceInstanceOperation.REMOVE.equals(recentlyChangedItem.serviceInstanceOperation)) {
                        Map<String, ServiceInstance> serviceInstanceMap = registry.get(serviceName);
                        if (serviceInstanceMap != null) {
                            serviceInstanceMap.remove(serviceInstanceId);
                        }
                    }
                }
            }
        }

        /**
         * 校对调整注册表
         * @param deltaRegistry
         */
        private void reconcileRegistry(DeltaRegistry deltaRegistry) {
            //Map<String, Map<String, ServiceInstance>> registry = applications.get().getRegistry();
            Map<String, Map<String, ServiceInstance>> registry = applications.getReference().getRegistry();
            Long serverSideTotalCount = deltaRegistry.getServiceInstanceTotalCount();

            Long clientSideTotalCount = 0L;
            for (Map<String, ServiceInstance> serviceInstanceMap : registry.values()) {
                clientSideTotalCount += serviceInstanceMap.size();
            }

            // 如果在拉取增量时发现服务端返回的注册表的实例数量和客户端注册表的实力数量不一致，则需要全量拉去注册表
            if (serverSideTotalCount != clientSideTotalCount) {
      /*          // 重新拉取全量注册表进行纠正
                // 人家正常的进行了全量注册最新数据的一个复制，可能是包含了40个实例
                // 最新数据
                Applications fetchedApplications = httpSender.fetchFullRegistry();
       *//*         while (true) {
                    Applications expectedApplications = applications.get();
                    if (applications.compareAndSet(expectedApplications, fetchedApplications)) {
                        break;
                    }
                }*//*
                while(true) {
                    Applications expectedApplications = applications.getReference();
                    int expectedStamp = applications.getStamp();
                    if(applications.compareAndSet(expectedApplications, fetchedApplications,
                            expectedStamp, expectedStamp + 1)) {
                        break;
                    }
                }*/
                // 重新拉取全量注册表进行纠正
                // 人家正常的进行了全量注册表最新数据的一个赋值，可能是包含了40个服务实例
                // 最新数据
                fetchFullRegistry();
            }
        }
    }

    /**
     * 服务实例操作
     * @author zhonghuashishan
     */
    class ServiceInstanceOperation {

        /**
         * 注册
         */
        public static final String REGISTER = "register";
        /**
         * 删除
         */
        public static final String REMOVE = "REMOVE";

    }

    /**
     * 获取服务注册表
     */
    public Map<String, Map<String, ServiceInstance>> getRegistry() {
        //return applications.get().getRegistry();
        return applications.getReference().getRegistry();
    }

    /**
     * 最近变更的实例信息
     */
    static class RecentlyChangedServiceInstance {

        /**
         * 服务实例
         */
        ServiceInstance serviceInstance;
        /**
         * 发生变更的时间戳
         */
        Long changedTimestamp;
        /**
         * 变更操作
         */
        String serviceInstanceOperation;

        public RecentlyChangedServiceInstance(
                ServiceInstance serviceInstance,
                Long changedTimestamp,
                String serviceInstanceOperation) {
            this.serviceInstance = serviceInstance;
            this.changedTimestamp = changedTimestamp;
            this.serviceInstanceOperation = serviceInstanceOperation;
        }

        @Override
        public String toString() {
            return "RecentlyChangedServiceInstance [serviceInstance=" + serviceInstance + ", changedTimestamp="
                    + changedTimestamp + ", serviceInstanceOperation=" + serviceInstanceOperation + "]";
        }

    }
}
